Attempting to use tidymodels for processing our IBM krill data and
selecting variables of importance for each swimming parameter
Inputs
rf_mod <- ## creates random forest model
rand_forest(trees = 1000) %>%
set_engine("ranger") %>%
set_mode("regression")
set.seed(234) ## comment out to get random runs
rf_fit <- ## fits random forest model to whole dataset
rf_mod %>%
fit(ave.v ~ flow * chl * guano * light, data = df)
rf_fit
rf_pred <- predict(rf_fit, df)
plot(df$ave.v, rf_pred$.pred, main = "corr - 0.7294325") ## observed vs predicted
cor <- cor.test(df$ave.v, rf_pred$.pred) ## gives correlation coef
rf_split <- initial_split(df %>% select(flow, chl, guano, light, ave.v), ## splits cases base on initial results
strata = NULL) ## with strata = NULL splits 11/31, rf_test has all guano absent, 50/50 light split, uneven flow split (0, 3, 3, 5.9, 5.9, 5.9, 8.9 x5), and random chl values.
## with strata = flow, splits 12/30, rf_test has 1 guano present, 50/50 light split, even flow splits (3x 0, 3, 4x 5.9 and 2x 8.9), and more even chl values.
## play with prop = 0.8, 0.9, etc
rf_train <- training(rf_split) ##creates training and testing datasets
rf_test <- testing(rf_split)
## comparisons to test data using ROC and accuracy to measure performance
rf_fit2 <- ## fits random forest model to training dataset
rf_mod %>%
fit(ave.v ~ flow * chl * guano * light, data = rf_train)
rf_fit2
rf_pred2 <- predict(rf_fit2, rf_test) ## compares to test data
plot(rf_test$ave.v, rf_pred2$.pred, main = "corr - 0.2700745") ## observed vs predicted, can add cor value from cor.test below
cor2 <- cor.test(rf_test$ave.v, rf_pred2$.pred) ## gives correlation coef
cor2$estimate
### can run with different seeds and splits, strata = NULL
#### loop and run 10 times with diff seeds
## look at consistency of results
Models
library(tidymodels) # for the parsnip package, along with the rest of tidymodels
── Attaching packages ─────────────────────────────────────────────────────────────────────────────────────────────── tidymodels 1.2.0 ──
✔ broom 1.0.5 ✔ rsample 1.2.1
✔ dials 1.2.1 ✔ tibble 3.2.1
✔ dplyr 1.1.4 ✔ tidyr 1.3.1
✔ infer 1.0.7 ✔ tune 1.2.0
✔ modeldata 1.3.0 ✔ workflows 1.1.4
✔ parsnip 1.2.1 ✔ workflowsets 1.1.0
✔ purrr 1.0.2 ✔ yardstick 1.3.1
✔ recipes 1.0.10
── Conflicts ────────────────────────────────────────────────────────────────────────────────────────────────── tidymodels_conflicts() ──
✖ dplyr::combine() masks randomForest::combine()
✖ purrr::discard() masks scales::discard()
✖ dplyr::filter() masks plotly::filter(), stats::filter()
✖ dplyr::lag() masks stats::lag()
✖ randomForest::margin() masks ggplot2::margin()
✖ recipes::step() masks stats::step()
• Search for functions across packages at https://www.tidymodels.org/find/
# Helper packages
library(readr) # for importing data
Attaching package: ‘readr’
The following object is masked from ‘package:yardstick’:
spec
The following object is masked from ‘package:scales’:
col_factor
library(broom.mixed) # for converting bayesian models to tidy tibbles
library(dotwhisker) # for visualizing regression results
library(vip) # for variable importance plots
Attaching package: ‘vip’
The following object is masked from ‘package:utils’:
vi
seeing if this fixes error
mean.vel.model <- readRDS(mean velocity.rds)
Error: unexpected symbol in "mean.vel.model <- readRDS(mean velocity.rds"
Node Purity heat map
### to save each plot as an individual graph
#jpeg(filename= paste('~/Bigelow/Figures/tidymodels Output/fit', i,'.jpeg', sep = ''), width = 960, height = 780)
#plot(df$response, rf_pred$.pred, main = paste("corr = ", cor$estimate)) ## observed vs predicted
#dev.off()
#best_tree <- rf.skill.test(df = df, trees = 2000, "ave.v", prop = 0.5, strata = NULL, do.plot = TRUE) ## not working....
#bt <- rbind(best_tree, bt)
## look at things outside random forest
rf_training_pred <-
predict(rf_fit, rf_train) %>%
bind_cols(predict(rf_fit, rf_train, type = "numeric")) %>%
# Add the true outcome data back in
bind_cols(rf_train %>%
select(flow, chl, guano, light))
rf_training_pred$light <- as.factor(rf_training_pred$light)
rf_training_pred$guano <- as.factor(rf_training_pred$guano)
rf_training_pred %>% # training set predictions
roc_auc(truth = light, .pred...1)
rf_training_pred %>% # training set predictions, only works for factors
accuracy(truth = ave.v, .pred...2)
## now that the model has exceptional performance lets move to the test dataset
rf_testing_pred <-
predict(rf_fit, rf_test) %>%
bind_cols(predict(rf_fit, rf_test, type = "numeric")) %>%
bind_cols(rf_test %>% select(flow, chl, guano, light))
rf_testing_pred$light <- as.factor(rf_testing_pred$light)
rf_testing_pred$guano <- as.factor(rf_testing_pred$guano)
rf_testing_pred %>% # test set predictions
roc_auc(truth = light, .pred...1)
rf_testing_pred %>% # test set predictions
accuracy(truth = light, guano)
## differences caused by training set error (bias) by model
###### resampling to the rescue
set.seed(345)
folds <- vfold_cv(rf_train, v = 10)
folds
rf_wf <- ## bundles workflow and random forest model together without a recipe needed
workflow() %>%
add_model(rf_mod) %>%
add_formula(ave.v ~ .)
set.seed(456)
rf_fit_rs <-
rf_wf %>%
fit_resamples(folds) ##fits resamples
rf_fit_rs ## .metrics column contains metrics on model performance
collect_metrics(rf_fit_rs) ##manually unnests meterics data
rf_testing_pred %>% # test set predictions (AS ABOVE)
roc_auc(truth = light, .pred...1)
rf_testing_pred %>% # test set predictions (AS ABOVE)
accuracy(truth = light, guano)
Tuning the model
library(glmnet)
library(rpart.plot) # for visualizing a decision tree
library(vip) # for variable importance plots
tune_spec <-
decision_tree( ## this is the type of model
cost_complexity = tune(),
tree_depth = tune()
) %>%
set_engine("rpart") %>%
set_mode("regression")
tune_spec
tree_grid <- grid_regular(cost_complexity(),
tree_depth(),
levels = 5)
tree_grid
tree_grid %>% ## shows each level we will tune the model at
count(tree_depth)
set.seed(234) ## don't understand what these do??
rf_folds <- vfold_cv(rf_train) ## creates cross-validation folds for tuning
set.seed(345)
tree_wf <- workflow() %>% ##creates the workflow
add_model(tune_spec) %>%
add_formula(ave.v ~ .)
tree_res <- ## resamples and tunes model
tree_wf %>%
tune_grid(
resamples = rf_folds,
grid = tree_grid
)
tree_res ## gives tuning results
tree_res %>%
collect_metrics() ## collects metrics from tuned models
tree_res %>%
collect_metrics() %>%
mutate(tree_depth = factor(tree_depth)) %>%
ggplot(aes(cost_complexity, mean, color = tree_depth)) +
geom_line(size = 1.5, alpha = 0.6) +
geom_point(size = 2) +
facet_wrap(~ .metric, scales = "free", nrow = 2) +
scale_x_log10(labels = scales::label_number()) +
scale_color_viridis_d(option = "plasma", begin = .9, end = 0)
## stubbiest tree with a depth of 1 performed the worst
## deepest tree with depth of 15 did better
tree_res %>%
show_best(metric = "rmse") ## shows best model fit
best_tree <- tree_res %>% ## pulls out data on the best fit
select_best(metric = "rmse")
best_tree ## summary of best tree model
final_wf <- ## create workflow from best tree model after tuning
tree_wf %>%
finalize_workflow(best_tree)
final_wf
final_fit <- ## create final model from new fit
final_wf %>%
last_fit(rf_split)
final_fit %>%
collect_metrics()
final_fit %>% ## plot ROC and compare performance after tuning
collect_predictions() %>%
roc_curve(flow, ave.v) %>% ### NOT WORKING
autoplot()
final_tree <- extract_workflow(final_fit) ## extract our final fit for future use
final_tree
final_tree %>% ## creates workflow plot
extract_fit_engine() %>%
rpart.plot(roundint = FALSE)
final_tree %>% ## shows which variables are most important to the model in a plot
extract_fit_parsnip() %>%
vip()
args(decision_tree)
Bigger RF Model
cores <- parallel::detectCores() ## sees how many cores we have to process the data
cores
rf_mod <- ## random forest model generation, parallel processing of models
rand_forest(mtry = tune(), min_n = tune(), trees = 1000) %>%
set_engine("ranger", num.threads = cores) %>%
set_mode("regression")
rf_recipe <- ## create random forest model recipe
recipe(ave.v ~ ., data = df)
rf_workflow <- ## create random forest model workflow
workflow() %>%
add_model(rf_mod) %>%
add_recipe(rf_recipe)
rf_mod
val_set <- validation_split(df,
strata = flow,
prop = 0.80)
val_set
# show what will be tuned
extract_parameter_set_dials(rf_mod)
set.seed(345)
rf_res <-
rf_workflow %>%
tune_grid(val_set,
grid = 25,
control = control_grid(save_pred = TRUE),
metrics = metric_set(rmse))
rf_res %>% ## shows 5 best random forest models out of the 25 candidates
show_best(metric = "rmse")
autoplot(rf_res) ## plot results
rf_best <- ## creates model with best predictors
rf_res %>%
select_best(metric = "rmse")
rf_best
rf_res %>% ## collects data for ROC curve plot
collect_predictions()
rf_best$mtry <- as.integer(rf_best$mtry)
##NOT WORKING
rf_auc <- ## creates set of models with best model and model model for comparison
rf_res %>%
collect_predictions(parameters = rf_best) %>%
roc_curve(mtry, .pred) %>%
mutate(model = "Random Forest")
##NOT WORKING
bind_rows(rf_best, rf_res) %>% ## plots model comparisons on ROC curve
ggplot(aes(x = 1 - specificity, y = sensitivity, col = model)) +
geom_path(lwd = 1.5, alpha = 0.8) +
geom_abline(lty = 3) +
coord_equal() +
scale_color_viridis_d(option = "plasma", end = .6)
################ last model after tuning
# the last model
last_rf_mod <-
rand_forest(mtry = 8, min_n = 7, trees = 1000) %>%
set_engine("ranger", num.threads = cores, importance = "impurity") %>%
set_mode("regression")
# the last workflow
last_rf_workflow <-
rf_workflow %>%
update_model(last_rf_mod)
# the last fit
set.seed(345)
last_rf_fit <-
last_rf_workflow %>%
last_fit(rf_split)
last_rf_fit
last_rf_fit %>% ## collect metrics from final model
collect_metrics()
last_rf_fit %>% ## updates model fit
extract_fit_parsnip() %>%
vip(num_features = 20)
##NOT WORKING
last_rf_fit %>% ## plots best ROC curve, with best set of hyperparameters as predictors
collect_predictions() %>%
roc_curve(ave.v, .pred...1) %>%
autoplot()
LS0tDQp0aXRsZTogIm5vdGVib29rMTYtdGlkeW1vZGVscyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQpBdHRlbXB0aW5nIHRvIHVzZSB0aWR5bW9kZWxzIGZvciBwcm9jZXNzaW5nIG91ciBJQk0ga3JpbGwgZGF0YSBhbmQgc2VsZWN0aW5nIHZhcmlhYmxlcyBvZiBpbXBvcnRhbmNlIGZvciBlYWNoIHN3aW1taW5nIHBhcmFtZXRlcg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeW1vZGVscykgICMgZm9yIHRoZSBwYXJzbmlwIHBhY2thZ2UsIGFsb25nIHdpdGggdGhlIHJlc3Qgb2YgdGlkeW1vZGVscw0KDQojIEhlbHBlciBwYWNrYWdlcw0KbGlicmFyeShyZWFkcikgICAgICAgIyBmb3IgaW1wb3J0aW5nIGRhdGENCmxpYnJhcnkoYnJvb20ubWl4ZWQpICMgZm9yIGNvbnZlcnRpbmcgYmF5ZXNpYW4gbW9kZWxzIHRvIHRpZHkgdGliYmxlcw0KbGlicmFyeShkb3R3aGlza2VyKSAgIyBmb3IgdmlzdWFsaXppbmcgcmVncmVzc2lvbiByZXN1bHRzDQpsaWJyYXJ5KHZpcCkgICAgICAgICAjIGZvciB2YXJpYWJsZSBpbXBvcnRhbmNlIHBsb3RzDQoNCg0Kcm0obGlzdD1scyhhbGw9VFJVRSkpICAgIyMgcmVtb3ZlcyB0aGUgcHJldmlvdXMgd29ya3NwYWNlIGFuZCBlbnZpcm9ubWVudCBzbyB0aGF0IHdlIG9ubHkgaGF2ZSB0aGUgZGF0YSB3ZSBuZWVkIGxvYWRlZCBpbiB0aGUgc2Vzc2lvbg0KbG9hZCgifi9Qb3N0LWRvYy9rcmlsbC10YW5rLWNvZGUvRGF0YVByb2Nlc3NpbmcvTm90ZWJvb2tzL1BhcmFtZXRlcml6YXRpb25Nb2RlbC5SZGF0YSIpDQoNCg0KZGltKHBhcmFtZXRlcnMpDQpnbGltcHNlKHBhcmFtZXRlcnMpDQoNCmRpbShjb25kaXRpb25zKQ0KZ2xpbXBzZShjb25kaXRpb25zKQ0KDQpkZiA8LSBkYXRhLmZyYW1lKGNvbmRpdGlvbnNbLDFdLGNvbmRpdGlvbnNbLDJdLGNvbmRpdGlvbnNbLDNdLGNvbmRpdGlvbnNbLDRdLHBhcmFtZXRlcnNbLDFdLCBwYXJhbWV0ZXJzWyw3XSkNCmNvbG5hbWVzKGRmKSA8LSBjKCJmbG93IiwiY2hsIiwgImd1YW5vIiwgImxpZ2h0IiwiYXZlLnYiLCAiZGlwLnRlc3QiKQ0KZGYNCmRmPC1uYS5vbWl0KGRmKQ0KDQoNCmF2ZS52IH4gZmxvdyAqIGNobCAqIGd1YW5vICogbGlnaHQNCg0KDQpsaW5lYXJfcmVnKCkNCg0KbGluZWFyX3JlZygpICU+JSANCiAgc2V0X2VuZ2luZSgia2VyYXMiKQ0KDQpsbV9tb2QgPC0gbGluZWFyX3JlZygpDQoNCmxtX2ZpdCA8LSANCiAgbG1fbW9kICU+JSANCiAgZml0KGF2ZS52IH4gZmxvdyAqIGNobCAqIGd1YW5vICogbGlnaHQsIGRhdGEgPSBkZikgICMjIGNyZWF0ZXMgYSBsaW5lYXIgbW9kZWwgKExNKQ0KbG1fZml0DQoNCnRpZHkobG1fZml0KSAgIyMgc3VtbWFyeSBvZiBtb2RlbA0KDQoNCnRpZHkobG1fZml0KSAlPiUgDQogIGR3cGxvdChkb3RfYXJncyA9IGxpc3Qoc2l6ZSA9IDIsIGNvbG9yID0gImJsYWNrIiksDQogICAgICAgICB3aGlza2VyX2FyZ3MgPSBsaXN0KGNvbG9yID0gImJsYWNrIiksDQogICAgICAgICB2bGluZSA9IGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIGNvbG91ciA9ICJncmV5NTAiLCBsaW5ldHlwZSA9IDIpKQ0KDQpuZXdfcG9pbnRzIDwtIGV4cGFuZC5ncmlkKGZsb3cgPSA2LjEsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGNobCA9IDE5LA0KICAgICAgICAgICAgICAgICAgICAgICAgICBndWFubyA9IDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGxpZ2h0ID0gMCkgIyMgY3JlYXRlIG5ldyBwb2ludHMNCm5ld19wb2ludHMNCg0KbWVhbl9wcmVkIDwtIHByZWRpY3QobG1fZml0LCBuZXdfZGF0YSA9IG5ld19wb2ludHMpICAjIyBtZWFuIHByZWRpY3RlZCBib2R5IHdpZHRoDQptZWFuX3ByZWQNCg0KY29uZl9pbnRfcHJlZCA8LSBwcmVkaWN0KGxtX2ZpdCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgbmV3X2RhdGEgPSBuZXdfcG9pbnRzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gImNvbmZfaW50IikgICMjIGNvbmZpZGVuY2UgaW50ZXJ2YWwgcHJlZGljdGlvbg0KY29uZl9pbnRfcHJlZA0KDQpwbG90X2RhdGEgPC0gDQogIG5ld19wb2ludHMgJT4lIA0KICBiaW5kX2NvbHMobWVhbl9wcmVkKSAlPiUgDQogIGJpbmRfY29scyhjb25mX2ludF9wcmVkKQ0KDQojIGFuZCBwbG90Og0KZ2dwbG90KHBsb3RfZGF0YSwgYWVzKHggPSBmbG93KSkgKyANCiAgZ2VvbV9wb2ludChhZXMoeSA9IC5wcmVkKSkgKyANCiAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbiA9IC5wcmVkX2xvd2VyLCANCiAgICAgICAgICAgICAgICAgICAgeW1heCA9IC5wcmVkX3VwcGVyKSwNCiAgICAgICAgICAgICAgICB3aWR0aCA9IC4yKSArIA0KICBsYWJzKHkgPSAiYXZlIHYiKQ0KDQojIHNldCB0aGUgcHJpb3IgZGlzdHJpYnV0aW9uDQpwcmlvcl9kaXN0IDwtIHJzdGFuYXJtOjpzdHVkZW50X3QoZGYgPSAxKQ0KDQpzZXQuc2VlZCgxMjMpDQoNCiMgbWFrZSB0aGUgcGFyc25pcCBtb2RlbA0KYmF5ZXNfbW9kIDwtICAgDQogIGxpbmVhcl9yZWcoKSAlPiUgDQogIHNldF9lbmdpbmUoInN0YW4iLCANCiAgICAgICAgICAgICBwcmlvcl9pbnRlcmNlcHQgPSBwcmlvcl9kaXN0LCANCiAgICAgICAgICAgICBwcmlvciA9IHByaW9yX2Rpc3QpIA0KDQojIHRyYWluIHRoZSBtb2RlbA0KYmF5ZXNfZml0IDwtIA0KICBiYXllc19tb2QgJT4lIA0KICBmaXQoYXZlLnYgfiBmbG93ICogY2hsICogZ3Vhbm8gKiBsaWdodCwgZGF0YSA9IGRmKQ0KDQpwcmludChiYXllc19maXQsIGRpZ2l0cyA9IDUpDQoNCnRpZHkoYmF5ZXNfZml0LCBjb25mLmludCA9IFRSVUUpDQoNCmJheWVzX3Bsb3RfZGF0YSA8LSANCiAgbmV3X3BvaW50cyAlPiUgDQogIGJpbmRfY29scyhwcmVkaWN0KGJheWVzX2ZpdCwgbmV3X2RhdGEgPSBuZXdfcG9pbnRzKSkgJT4lIA0KICBiaW5kX2NvbHMocHJlZGljdChiYXllc19maXQsIG5ld19kYXRhID0gbmV3X3BvaW50cywgdHlwZSA9ICJjb25mX2ludCIpKQ0KDQpnZ3Bsb3QoYmF5ZXNfcGxvdF9kYXRhLCBhZXMoeCA9IGZsb3cpKSArIA0KICBnZW9tX3BvaW50KGFlcyh5ID0gLnByZWQpKSArIA0KICBnZW9tX2Vycm9yYmFyKGFlcyh5bWluID0gLnByZWRfbG93ZXIsIHltYXggPSAucHJlZF91cHBlciksIHdpZHRoID0gLjIpICsgDQogIGxhYnMoeSA9ICJhdmUudiIpICsgDQogIGdndGl0bGUoIkJheWVzaWFuIG1vZGVsIHdpdGggdCgxKSBwcmlvciBkaXN0cmlidXRpb24iKQ0KDQojIyMgVHVuaW5nIHRoZSBtb2RlbCBpbiBzZWN0aW9ucw0KDQpkZiAlPiUgDQogIGdyb3VwX2J5KGxpZ2h0KSAlPiUgDQogIHN1bW1hcml6ZShtZWRfZmxvdyA9IG1lZGlhbihmbG93KSkNCg0KYmF5ZXNfbW9kICU+JSANCiAgZml0KGF2ZS52IH4gZmxvdyAqIGNobCAqIGd1YW5vICogbGlnaHQsIGRhdGEgPSBkZikNCg0KZ2dwbG90KGRmLA0KICAgICAgIGFlcyhmbG93LCBhdmUudikpICsgICAgICAjIHJldHVybnMgYSBnZ3Bsb3Qgb2JqZWN0IA0KICBnZW9tX2ppdHRlcigpICsgICAgICAgICAgICAgICAgICAgICAgICAgIyBzYW1lDQogIGdlb21fc21vb3RoKG1ldGhvZCA9IGxtLCBzZSA9IEZBTFNFKSArICAjIHNhbWUgICAgICAgICAgICAgICAgICAgIA0KICBsYWJzKHggPSAiZmxvdyIsIHkgPSAiYXZlLnYiKSAgICAgICAgICMgZXRjDQpgYGANCklucHV0cw0KDQpgYGB7cn0NCg0KcmZfbW9kIDwtICMjIGNyZWF0ZXMgcmFuZG9tIGZvcmVzdCBtb2RlbA0KICByYW5kX2ZvcmVzdCh0cmVlcyA9IDEwMDApICU+JSANCiAgc2V0X2VuZ2luZSgicmFuZ2VyIikgJT4lIA0KICBzZXRfbW9kZSgicmVncmVzc2lvbiIpDQoNCg0Kc2V0LnNlZWQoMjM0KSAgIyMgY29tbWVudCBvdXQgdG8gZ2V0IHJhbmRvbSBydW5zDQoNCnJmX2ZpdCA8LSAjIyBmaXRzIHJhbmRvbSBmb3Jlc3QgbW9kZWwgdG8gd2hvbGUgZGF0YXNldA0KICByZl9tb2QgJT4lIA0KICBmaXQoYXZlLnYgfiBmbG93ICogY2hsICogZ3Vhbm8gKiBsaWdodCwgZGF0YSA9IGRmKQ0KcmZfZml0DQoNCnJmX3ByZWQgPC0gcHJlZGljdChyZl9maXQsIGRmKSAgDQpwbG90KGRmJGF2ZS52LCByZl9wcmVkJC5wcmVkLCBtYWluID0gImNvcnIgLSAwLjcyOTQzMjUiKSAgIyMgb2JzZXJ2ZWQgdnMgcHJlZGljdGVkDQpjb3IgPC0gY29yLnRlc3QoZGYkYXZlLnYsIHJmX3ByZWQkLnByZWQpICAjIyBnaXZlcyBjb3JyZWxhdGlvbiBjb2VmDQoNCnJmX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQoZGYgJT4lIHNlbGVjdChmbG93LCBjaGwsIGd1YW5vLCBsaWdodCwgYXZlLnYpLCAjIyBzcGxpdHMgY2FzZXMgYmFzZSBvbiBpbml0aWFsIHJlc3VsdHMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJhdGEgPSBOVUxMKSAgIyMgd2l0aCBzdHJhdGEgPSBOVUxMIHNwbGl0cyAxMS8zMSwgcmZfdGVzdCBoYXMgYWxsIGd1YW5vIGFic2VudCwgNTAvNTAgbGlnaHQgc3BsaXQsIHVuZXZlbiBmbG93IHNwbGl0ICgwLCAzLCAzLCA1LjksIDUuOSwgNS45LCA4LjkgeDUpLCBhbmQgcmFuZG9tIGNobCB2YWx1ZXMuDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMjIHdpdGggc3RyYXRhID0gZmxvdywgc3BsaXRzIDEyLzMwLCByZl90ZXN0IGhhcyAxIGd1YW5vIHByZXNlbnQsIDUwLzUwIGxpZ2h0IHNwbGl0LCBldmVuIGZsb3cgc3BsaXRzICgzeCAwLCAzLCA0eCA1LjkgYW5kIDJ4IDguOSksIGFuZCBtb3JlIGV2ZW4gY2hsIHZhbHVlcy4NCiMjIHBsYXkgd2l0aCBwcm9wID0gMC44LCAwLjksIGV0Yw0KDQoNCnJmX3RyYWluIDwtIHRyYWluaW5nKHJmX3NwbGl0KSAgIyNjcmVhdGVzIHRyYWluaW5nIGFuZCB0ZXN0aW5nIGRhdGFzZXRzDQpyZl90ZXN0ICA8LSB0ZXN0aW5nKHJmX3NwbGl0KQ0KDQojIyBjb21wYXJpc29ucyB0byB0ZXN0IGRhdGEgdXNpbmcgUk9DIGFuZCBhY2N1cmFjeSB0byBtZWFzdXJlIHBlcmZvcm1hbmNlDQoNCnJmX2ZpdDIgPC0gIyMgZml0cyByYW5kb20gZm9yZXN0IG1vZGVsIHRvIHRyYWluaW5nIGRhdGFzZXQNCiAgcmZfbW9kICU+JSANCiAgZml0KGF2ZS52IH4gZmxvdyAqIGNobCAqIGd1YW5vICogbGlnaHQsIGRhdGEgPSByZl90cmFpbikNCnJmX2ZpdDINCg0KcmZfcHJlZDIgPC0gcHJlZGljdChyZl9maXQyLCByZl90ZXN0KSAjIyBjb21wYXJlcyB0byB0ZXN0IGRhdGENCnBsb3QocmZfdGVzdCRhdmUudiwgcmZfcHJlZDIkLnByZWQsIG1haW4gPSAiY29yciAtIDAuMjcwMDc0NSIpICAjIyBvYnNlcnZlZCB2cyBwcmVkaWN0ZWQsIGNhbiBhZGQgY29yIHZhbHVlIGZyb20gY29yLnRlc3QgYmVsb3cNCmNvcjIgPC0gY29yLnRlc3QocmZfdGVzdCRhdmUudiwgcmZfcHJlZDIkLnByZWQpICAjIyBnaXZlcyBjb3JyZWxhdGlvbiBjb2VmDQpjb3IyJGVzdGltYXRlDQojIyMgY2FuIHJ1biB3aXRoIGRpZmZlcmVudCBzZWVkcyBhbmQgc3BsaXRzLCBzdHJhdGEgPSBOVUxMDQojIyMjIGxvb3AgYW5kIHJ1biAxMCB0aW1lcyB3aXRoIGRpZmYgc2VlZHMNCiMjIGxvb2sgYXQgY29uc2lzdGVuY3kgb2YgcmVzdWx0cw0KYGBgDQoNCk1vZGVscw0KYGBge3J9DQojIyMjIyBFeGFtcGxlIG1vZGVscw0KbGlicmFyeSh0aWR5bW9kZWxzKSAgIyBmb3IgdGhlIHBhcnNuaXAgcGFja2FnZSwgYWxvbmcgd2l0aCB0aGUgcmVzdCBvZiB0aWR5bW9kZWxzDQoNCiMgSGVscGVyIHBhY2thZ2VzDQpsaWJyYXJ5KHJlYWRyKSAgICAgICAjIGZvciBpbXBvcnRpbmcgZGF0YQ0KbGlicmFyeShicm9vbS5taXhlZCkgIyBmb3IgY29udmVydGluZyBiYXllc2lhbiBtb2RlbHMgdG8gdGlkeSB0aWJibGVzDQpsaWJyYXJ5KGRvdHdoaXNrZXIpICAjIGZvciB2aXN1YWxpemluZyByZWdyZXNzaW9uIHJlc3VsdHMNCmxpYnJhcnkodmlwKSAgICAgICAgICMgZm9yIHZhcmlhYmxlIGltcG9ydGFuY2UgcGxvdHMNCg0KDQpybShsaXN0PWxzKGFsbD1UUlVFKSkgICAjIyByZW1vdmVzIHRoZSBwcmV2aW91cyB3b3Jrc3BhY2UgYW5kIGVudmlyb25tZW50IHNvIHRoYXQgd2Ugb25seSBoYXZlIHRoZSBkYXRhIHdlIG5lZWQgbG9hZGVkIGluIHRoZSBzZXNzaW9uDQpsb2FkKCJ+L0JpZ2Vsb3cvRGF0YS9QYXJhbWV0ZXJpemF0aW9uTW9kZWwuMTUuMDcuMjQuUmRhdGEiKQ0KYGBgDQoNCnNlZWluZyBpZiB0aGlzIGZpeGVzIGVycm9yDQpgYGB7cn0NCmRpbShwYXJhbWV0ZXJzKQ0KZ2xpbXBzZShwYXJhbWV0ZXJzKQ0KDQpkaW0oY29uZGl0aW9ucykNCmdsaW1wc2UoY29uZGl0aW9ucykNCg0KZGYgPC0gZGF0YS5mcmFtZShjb25kaXRpb25zWywxXSxjb25kaXRpb25zWywyXSxjb25kaXRpb25zWywzXSxjb25kaXRpb25zWyw0XSxwYXJhbWV0ZXJzWywxXSkNCmNvbG5hbWVzKGRmKSA8LSBjKCJmbG93IiwiY2hsIiwgImd1YW5vIiwgImxpZ2h0Iixjb2xuYW1lcyhwYXJhbWV0ZXJzKVsxXSkNCmRmDQpkZjwtbmEub21pdChkZikNCg0Kc291cmNlKCJub3RlYm9vazE2LWZ1bmN0aW9ucy5SIikNCiMjIGZpeCB0byBwdWxsIGNvbCBuYW1lIG9yIG51bWJlciBpbnRvIG1vZGVsPz8NCmNvbG5hbWVzKGRmKSA8LSBjKCJmbG93IiwiY2hsIiwgImd1YW5vIiwgImxpZ2h0IiwgY29sbmFtZXMocGFyYW1ldGVycylbMV0pDQpjLnYgPC0gTlVMTA0KYy52X3RyYWluIDwtIE5VTEwNCnAgPC0gTlVMTA0KcnNxMiA8LSBOVUxMDQptb2QxIDwtIE5VTEwNCm1vZDIgPC0gTlVMTA0KbW9kMyA8LSBOVUxMDQpub2RlLnB1cml0eSA8LSBkYXRhLmZyYW1lKG1hdHJpeCAoTkEsIG5yb3cgPSAxMCwgbmNvbCA9IDUpKQ0KY29sbmFtZXMobm9kZS5wdXJpdHkpIDwtIGMoICJjLmUiLCAiYy5lLnQiLCAici5zcXVhcmVkIiwgInAtdmFsdWUiLCAibm9kZSBwdXJpdHkiKQ0KDQoNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoR0dhbGx5KQ0KbGlicmFyeShoZXhiaW4pDQpsaWJyYXJ5KGRpcHRlc3QpDQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCmxpYnJhcnkocmVwcnRyZWUpDQoNCiMjIGxvb3AgdGhyb3VnaCBkaWZmZXJlbnQgc3dpbW1pbmcgcGFyYW1ldGVycyBjb250YWluIHRoaXMgbG9vcCB3aXRoaW4gdGhlIGJpZ2dlciBsb29wDQpjb2xuYW1lcyhwYXJhbWV0ZXJzKQ0KDQoNCmZvciAoaiBpbiBjb2xuYW1lcyhwYXJhbWV0ZXJzKSl7DQogICAgZGYgPC0gZGF0YS5mcmFtZShjb25kaXRpb25zWywxXSxjb25kaXRpb25zWywyXSxjb25kaXRpb25zWywzXSxjb25kaXRpb25zWyw0XSwgcGFyYW1ldGVyc1ssal0pDQogICAgY29sbmFtZXMoZGYpIDwtIGMoImZsb3ciLCJjaGwiLCAiZ3Vhbm8iLCAibGlnaHQiLCBqKQ0KICAgIGRmDQogICAgZGY8LW5hLm9taXQoZGYpDQogICAgc291cmNlKCJub3RlYm9vazE2LWZ1bmN0aW9ucy5SIikNCiAgICBwcmludChjb2xuYW1lcyhkZikpDQoNCiAgICAgIGZvciAoaSBpbiAxOjEwKXsgIA0KDQogICAgICAgIGMuZSA8LSByZi5za2lsbChkZiA9IGRmLCB0cmVlcyA9IDIwMDAsIGNvbDEgPSBjb2xuYW1lcyhkZilbNV0pIA0KICAgICAgICBjLnYgPC0gcmJpbmQoYy5lLCBjLnYpICANCiAgICAgICAgYy5lLnQgPC0gcmYuc2tpbGwudGVzdChkZiA9IGRmLCB0cmVlcyA9IDIwMDAsIGNvbDEgPSBjb2xuYW1lcyhkZilbNV0sIHByb3AgPSAwLjgsIHN0cmF0YSA9IE5VTEwsIGRvLnBsb3QgPSBGQUxTRSkgICAjIyB0cmFpbmluZyBkYXRhDQogICAgICAgIGMudl90cmFpbiA8LSByYmluZChjLmUudCwgYy52X3RyYWluKQ0KICAgICAgICByc3ExIDwtIHJmLmZpdChkZiA9IGRmLCB0cmVlcyA9IDIwMDAsIGNvbDEgPSBjb2xuYW1lcyhkZilbNV0pDQogICAgICAgIHJzcTIgPC0gcmJpbmQocnNxMSwgcnNxMikNCiAgDQogICAgICAgIG91dHB1dC50ZXN0IDwtIG1vZGVsLnRlc3QoZGYgPSBkZiwgdHJlZXMgPSAyMDAwLCBjb2wxID0gY29sbmFtZXMoZGYpWzVdLCBwcm9wID0gMC44LCBzdHJhdGEgPSBOVUxMLCBkby5wbG90ID0gVFJVRSkNCiAgICAgICAgDQogICAgICAgICMjIG91dHB1dCBub2RlIHB1cml0eQ0KICANCiAgICAgICAgIyMgY3JlYXRlIGZ1bmN0aW9uIHRvIGZpbHRlciBlZGdlIGVmZmVjdCBvdXQgKC41IGNtIG91dCwgZGlzdCB0byBlZGdlKQ0KICANCiAgICAgICAgIyNnZ3NhdmUoZmlsZW5hbWU9cGFzdGUoJ34vQmlnZWxvdy9GaWd1cmVzL3RpZHltb2RlbHMgT3V0cHV0L2F2ZSB2LycsIGksICcudGlmZicsIHNlcCA9ICcnKSwgcGxvdCA9IHAsICAgICB3aWR0aCA9IDI0ICwgaGVpZ2h0ID0gMTIpDQogICAgICB9DQogICAgbW9kMSA8LSByYmluZChtb2QxLCBjLnYpICAjIyBhZGQgaWRlbnRpZmllciBmb3IgZWFjaCBwYXJhbWV0ZXIgaW50byBkYXRhIGZyYW1lDQogICAgbW9kMiA8LSByYmluZChtb2QyLCBjLnZfdHJhaW4pDQogICAgbW9kMyA8LSByYmluZChtb2QzLCByc3EyKQ0KICAgIA0KICAgICMjIGlucHV0IGF2ZXJhZ2Ugb2YgcCB2YWx1ZSwgY2UsIGN2LCBub2RlIHB1cml0eSBhbmQgcnNxIGludG8gZWFjaCBjb2x1bW4gYW5kIHJvdyBpbiBtYXRyaXggZm9yIGVhY2ggcGFyYW1ldGVyIGFuZCBtb2RlbCB0eXBlDQogICAgDQogICAgIyMgcmFuayBvbiBiZXN0IGZpdA0KICAgICMjIHRhYmxlIGZvciByZXBvcnQgb2Ygc3RhdHMgb24gbW9kZWxzDQogICAgDQp9DQoNCiMjIHRha2UgYmVzdCBtb2RlbCBmb3IgZWFjaCBwYXJhcm1ldGVyIGludG8gc2ltdWxhdGlvbg0KIyMgdHJ5IHZlbCBtZWFuIGFuZCBzZCwgYW5kIHR1cm4gbWVhbiBhbmQgc2QgDQoNCnJmX3ByZWRfdHJhaW4gPC0gcHJlZGljdChyZl9maXRfdHJhaW4sIHJmX3Rlc3QpICMjIG5lZWQgdG8gcHJlZGljdCBlYWNoIHBhcmFtZXRlciB3aXRoaW4gc2ltdWxhdGlvbg0KDQojIyBlbnZpcm9ubWVudGFsIGlucHV0IHRvIHN3aW1taW5nLCBlZy4gbm9kZSBwdXJpdHkNCg0KDQoNCmMudiA8LSBhcy5kYXRhLmZyYW1lKGMudikNCmMudl90cmFpbiA8LSBhcy5kYXRhLmZyYW1lKGMudl90cmFpbikNCnJzcTIgPC0gYXMuZGF0YS5mcmFtZShyc3EyKQ0KDQpub2RlLnB1cml0eSRjLmUgPC0gYy52JFYxDQpub2RlLnB1cml0eSRjLmUudCA8LSBjLnZfdHJhaW4kY29yDQpub2RlLnB1cml0eSRyLnNxdWFyZWQgPC0gcnNxMiRWMQ0KDQojIyBzYXZlIGVhY2ggcGFyYW1ldGVyIG1vZGVsIGluZGl2aWR1YWxseSBvciBhcyBzaW5nbGUgZmlsZSAoc2luZ2xlIFJEYXRhIGZpbGUgLSBpbmRpdmlkdWFsIG9iamVjdHMgdGhlcmVpbikgLSBpbmNsdWRlIHJlYWRtZS5kb2MgZm9yIHdoYXQgZWFjaCBvYmplY3QgaW4gZW52aXJvbm1lbnQgaXMNCg0Kc2F2ZVJEUyhSRl90ZXN0X3Jlc3VsdHMsIGZpbGUgPSAifi9CaWdlbG93L0RhdGEvU3dpbW1pbmcgUGFyYW1ldGVyIE1vZGVscy9tZWFuIHZlbG9jaXR5LnJkcyIpDQpzYXZlUkRTKGJlc3RfcmVzdWx0cywgZmlsZSA9ICJ+L0JpZ2Vsb3cvRGF0YS9Td2ltbWluZyBQYXJhbWV0ZXIgTW9kZWxzL21lYW4gdmVsb2NpdHkgLSBiZXN0LnJkcyIpDQoNCm1lYW4udmVsLm1vZGVsIDwtIHJlYWRSRFMoIn4vQmlnZWxvdy9EYXRhL1N3aW1taW5nIFBhcmFtZXRlciBNb2RlbHMvbWVhbiB2ZWxvY2l0eS5yZHMiKSAgIyNjYW4gbmFtZSBtb2RlbHMgYXMgdGhleSBhcmUgbG9hZGVkIGluIHVubGlrZSB3aXRoIHJlZ3VsYXIgc2F2ZSAoKSBhbmQgbG9hZCAoKSBmdW5jdGlvbnMNCg0KDQpgYGANCg0KTm9kZSBQdXJpdHkgaGVhdCBtYXANCmBgYHtyfQ0KIyMjIyMgRXhhbXBsZSBkYXRhDQpzZXQuc2VlZCgxMjMpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFNldCBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkNCmRhdGE8LSBtYXRyaXgocm5vcm0oMTAwLCAwLCAxMCksIG5yb3cgPSAxMCwgbmNvbCA9IDEwKSAgICAgICAgICAgIyBDcmVhdGUgZXhhbXBsZSBkYXRhICAgICAgICAgICAgICAgICAgICAgICMgQXBwbHkgaGVhdG1hcCBmdW5jdGlvbg0KDQpjb2xuYW1lcyhkYXRhKTwtIHBhc3RlMCgiY29sIiwgMToxMCkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQ29sdW1uIG5hbWVzDQpyb3duYW1lcyhkYXRhKTwtIHBhc3RlMCgicm93IiwgMToxMCkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgUm93IG5hbWVzDQpoZWFkKGRhdGEsNSkNCg0Kbm9kZSA8LSByZWFkLmNzdigiQzpcXFVzZXJzXFxOaWNvbGUgSGVsbGVzc2V5XFxEb2N1bWVudHNcXEJpZ2Vsb3dcXERhdGFcXE5vZGUgUHVyaXR5IFZhbHVlcyAtIE1lbHRlZC5jc3YiLCBoZWFkZXIgPSBUKQ0KaGVhZChub2RlKQ0Kbm9kZSR2YXJpYWJsZSA8LSBwYXN0ZShub2RlJFN3aW1taW5nLlBhcmFtZXRlcixub2RlJFN3aW1taW5nLkZhY3Rvciwgc2VwID0gIi0iKQ0Kc3RyKG5vZGUpDQpub2RlJHZhcmlhYmxlIDwtIGFzLmZhY3Rvcihub2RlJHZhcmlhYmxlKQ0Kbm9kZSRTd2ltbWluZy5GYWN0b3IgPC0gb3JkZXJlZChub2RlJFN3aW1taW5nLkZhY3RvciwgbGV2ZWxzID0gYygiVmVsb2NpdHkiLCAiSG9yaXpvbnRhbCBIZWFkaW5nIiwgIlZlcnRpY2FsIEhlYWRpbmciLCAiVG90YWwiKSkNCm5vZGUyIDwtIGRhdGEubWF0cml4KG5vZGUpDQoNCiMjIG5vcm1hbGlzZSBub2RlIHB1cml0eSB2YWx1ZXMgYW5kIHJlYWQgdXAgb24gd2hhdCBpdCBpcw0KIyMgcHJvZHVjZSBzaW1pbGFyIHBsb3RzIGZvciBSU1EgYW5kIFJNU0U/Pw0KDQoNCiMjIyMjbm9kZSMjIyMjIEV4YW1wbGUgMQ0KaGVhdG1hcChub2RlMikgIA0KDQojIyMjIyBFeGFtcGxlIDINCmhlYXRtYXAobm9kZTIsIFJvd3YgPSBOQSwgQ29sdiA9IE5BKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFJlbW92ZSBkZW5kb2dyYW0NCg0KIyMjIyMgRXhhbXBsZSAzDQpteV9jb2xvcnM8LSBjb2xvclJhbXBQYWxldHRlKGMoImN5YW4iLCAiZGVlcHBpbmszIikpICAgICAgICAgICAgICMgTWFudWFsIGNvbG9yIHJhbmdlDQpoZWF0bWFwKG5vZGUyLCBjb2wgPSBteV9jb2xvcnMoMTAwKSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBIZWF0bWFwIHdpdGggbWFudWFsIGNvbG9ycw0KDQojIyMjIyBFeGFtcGxlIDQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgSW5zdGFsbCByZXNoYXBlIHBhY2thZ2UNCmxpYnJhcnkocmVzaGFwZSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIExvYWQgcmVzaGFwZSBwYWNrYWdlDQoNCm5vZGVfbWVsdCA8LSBtZWx0KG5vZGUpICANCmhlYWQobm9kZV9tZWx0KSMgUmVvcmRlciBkYXRhDQpsaWJyYXJ5KGdncGxvdDIpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBMb2FkIGdncGxvdDIgcGFja2FnZQ0KDQpnZ3AgPC0gZ2dwbG90KG5vZGUsIGFlcyhTd2ltbWluZy5QYXJhbWV0ZXIsIFN3aW1taW5nLkZhY3RvcikpICsgICAgICAgICAgICAgICAgICAgICAgICAgICAjIENyZWF0ZSBoZWF0bWFwIHdpdGggZ2dwbG90Mg0KICBnZW9tX3RpbGUoYWVzKGZpbGwgPSBWYWx1ZSkpICsgDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gIndoaXRlIiwgaGlnaCA9ICJibGFjayIpICsNCiAgdGhlbWVfY2xhc3NpYygpKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3Q9MSkpICsgDQogIGZhY2V0X2dyaWQofkVudmlyb25tZW50YWwuSW5wdXQsIHNjYWxlcyA9ICJmcmVlX3giLCBzcGFjZSA9ICJmcmVlIikgKyANCiAgbGFicyh4ID0gIlN3aW1taW5nIFBhcmFtZXRlciIsIHkgPSAiU3dpbW1pbmcgRmFjdG9yIiwgdGl0bGUgPSAiTm9kZSBQdXJpdHkgSGVhdG1hcCIsIGZpbGwgPSAiTm9kZSBQdXJpdHkgVmFsdWUiKQ0KDQpsaWJyYXJ5KHBsb3RseSkNCmdncGxvdGx5KGdncCkNCg0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KDQphdHRhY2gobm9kZV9tZWx0KQ0KDQpnZ3Bsb3Qobm9kZSwgYWVzKHggPSB2YXJpYWJsZSwgeSA9IEVudmlyb25tZW50YWwuSW5wdXQsIGZpbGwgPSB2YWx1ZSkpICsNCiAgZ2VvbV90aWxlKCkgKw0KICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJ3aGl0ZSIsIGhpZ2ggPSAicmVkIikgKw0KICBsYWJzKHggPSAiRW52aXJvbm1lbnRhbCBDb25kaXRpb24iLCB5ID0gIlN3aW1taW5nIFBhcmFtZXRlciIsIHRpdGxlID0gIk5vZGUgUHVyaXR5IEhlYXRtYXAgLSBPdmVyYWxsIFByZWRpY3RvcnMiKQ0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCiMgbG9hZCByZXF1aXJlZCBwYWNrYWdlcw0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KDQojIGNyZWF0ZSBoZWF0bWFwIHVzaW5nIHBsb3RseQ0KcGxvdF9seShub2RlLCB4ID0gbm9kZSRTd2ltbWluZy5GYWN0b3IsIHkgPSBub2RlJFN3aW1taW5nLlBhcmFtZXRlciwgeiA9IG5vZGUkVmFsdWUsIHR5cGUgPSAiaGVhdG1hcCIpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAiSGVhdG1hcCIsIHhheGlzID0gbGlzdCh0aXRsZSA9ICIiKSwgeWF4aXMgPSBsaXN0KHRpdGxlID0gIiIpKQ0KDQpgYGANCg0KDQpgYGB7cn0NCiAgIyMjIHRvIHNhdmUgZWFjaCBwbG90IGFzIGFuIGluZGl2aWR1YWwgZ3JhcGgNCiAgI2pwZWcoZmlsZW5hbWU9IHBhc3RlKCd+L0JpZ2Vsb3cvRmlndXJlcy90aWR5bW9kZWxzIE91dHB1dC9maXQnLCBpLCcuanBlZycsIHNlcCA9ICcnKSwgd2lkdGggPSA5NjAsICAgICAgaGVpZ2h0ID0gNzgwKQ0KICAjcGxvdChkZiRyZXNwb25zZSwgcmZfcHJlZCQucHJlZCwgbWFpbiA9IHBhc3RlKCJjb3JyID0gIiwgY29yJGVzdGltYXRlKSkgICMjIG9ic2VydmVkIHZzIHByZWRpY3RlZA0KICAjZGV2Lm9mZigpDQoNCg0KICAjYmVzdF90cmVlIDwtIHJmLnNraWxsLnRlc3QoZGYgPSBkZiwgdHJlZXMgPSAyMDAwLCAiYXZlLnYiLCBwcm9wID0gMC41LCBzdHJhdGEgPSBOVUxMLCBkby5wbG90ID0gVFJVRSkgIyMgbm90IHdvcmtpbmcuLi4uDQogICNidCA8LSByYmluZChiZXN0X3RyZWUsIGJ0KQ0KDQojIyBsb29rIGF0IHRoaW5ncyBvdXRzaWRlIHJhbmRvbSBmb3Jlc3QNCg0KDQpyZl90cmFpbmluZ19wcmVkIDwtIA0KICBwcmVkaWN0KHJmX2ZpdCwgcmZfdHJhaW4pICU+JSANCiAgYmluZF9jb2xzKHByZWRpY3QocmZfZml0LCByZl90cmFpbiwgdHlwZSA9ICJudW1lcmljIikpICU+JSANCiAgIyBBZGQgdGhlIHRydWUgb3V0Y29tZSBkYXRhIGJhY2sgaW4NCiAgYmluZF9jb2xzKHJmX3RyYWluICU+JSANCiAgICAgICAgICAgICAgc2VsZWN0KGZsb3csIGNobCwgZ3Vhbm8sIGxpZ2h0KSkNCg0KcmZfdHJhaW5pbmdfcHJlZCRsaWdodCA8LSBhcy5mYWN0b3IocmZfdHJhaW5pbmdfcHJlZCRsaWdodCkNCnJmX3RyYWluaW5nX3ByZWQkZ3Vhbm8gPC0gYXMuZmFjdG9yKHJmX3RyYWluaW5nX3ByZWQkZ3Vhbm8pDQoNCnJmX3RyYWluaW5nX3ByZWQgJT4lICAgICAgICAgICAgICAgICMgdHJhaW5pbmcgc2V0IHByZWRpY3Rpb25zDQogIHJvY19hdWModHJ1dGggPSBsaWdodCwgLnByZWQuLi4xKQ0KDQpyZl90cmFpbmluZ19wcmVkICU+JSAgICAgICAgICAgICAgICAjIHRyYWluaW5nIHNldCBwcmVkaWN0aW9ucywgb25seSB3b3JrcyBmb3IgZmFjdG9ycw0KICBhY2N1cmFjeSh0cnV0aCA9IGF2ZS52LCAucHJlZC4uLjIpDQoNCiMjIG5vdyB0aGF0IHRoZSBtb2RlbCBoYXMgZXhjZXB0aW9uYWwgcGVyZm9ybWFuY2UgbGV0cyBtb3ZlIHRvIHRoZSB0ZXN0IGRhdGFzZXQNCg0KcmZfdGVzdGluZ19wcmVkIDwtIA0KICBwcmVkaWN0KHJmX2ZpdCwgcmZfdGVzdCkgJT4lIA0KICBiaW5kX2NvbHMocHJlZGljdChyZl9maXQsIHJmX3Rlc3QsIHR5cGUgPSAibnVtZXJpYyIpKSAlPiUgDQogIGJpbmRfY29scyhyZl90ZXN0ICU+JSBzZWxlY3QoZmxvdywgY2hsLCBndWFubywgbGlnaHQpKQ0KDQpyZl90ZXN0aW5nX3ByZWQkbGlnaHQgPC0gYXMuZmFjdG9yKHJmX3Rlc3RpbmdfcHJlZCRsaWdodCkNCnJmX3Rlc3RpbmdfcHJlZCRndWFubyA8LSBhcy5mYWN0b3IocmZfdGVzdGluZ19wcmVkJGd1YW5vKQ0KDQpyZl90ZXN0aW5nX3ByZWQgJT4lICAgICAgICAgICAgICAgICAgICMgdGVzdCBzZXQgcHJlZGljdGlvbnMNCiAgcm9jX2F1Yyh0cnV0aCA9IGxpZ2h0LCAucHJlZC4uLjEpDQoNCnJmX3Rlc3RpbmdfcHJlZCAlPiUgICAgICAgICAgICAgICAgICAgIyB0ZXN0IHNldCBwcmVkaWN0aW9ucw0KICBhY2N1cmFjeSh0cnV0aCA9IGxpZ2h0LCBndWFubykNCg0KIyMgZGlmZmVyZW5jZXMgY2F1c2VkIGJ5IHRyYWluaW5nIHNldCBlcnJvciAoYmlhcykgYnkgbW9kZWwNCg0KIyMjIyMjIHJlc2FtcGxpbmcgdG8gdGhlIHJlc2N1ZQ0KDQpzZXQuc2VlZCgzNDUpDQpmb2xkcyA8LSB2Zm9sZF9jdihyZl90cmFpbiwgdiA9IDEwKQ0KZm9sZHMNCg0KcmZfd2YgPC0gIyMgYnVuZGxlcyB3b3JrZmxvdyBhbmQgcmFuZG9tIGZvcmVzdCBtb2RlbCB0b2dldGhlciB3aXRob3V0IGEgcmVjaXBlIG5lZWRlZA0KICB3b3JrZmxvdygpICU+JQ0KICBhZGRfbW9kZWwocmZfbW9kKSAlPiUNCiAgYWRkX2Zvcm11bGEoYXZlLnYgfiAuKQ0KDQpzZXQuc2VlZCg0NTYpDQpyZl9maXRfcnMgPC0gDQogIHJmX3dmICU+JSANCiAgZml0X3Jlc2FtcGxlcyhmb2xkcykgICMjZml0cyByZXNhbXBsZXMNCg0KcmZfZml0X3JzICAjIyAubWV0cmljcyBjb2x1bW4gY29udGFpbnMgbWV0cmljcyBvbiBtb2RlbCBwZXJmb3JtYW5jZQ0KDQpjb2xsZWN0X21ldHJpY3MocmZfZml0X3JzKSAgIyNtYW51YWxseSB1bm5lc3RzIG1ldGVyaWNzIGRhdGENCg0KcmZfdGVzdGluZ19wcmVkICU+JSAgICAgICAgICAgICAgICAgICAjIHRlc3Qgc2V0IHByZWRpY3Rpb25zIChBUyBBQk9WRSkNCiAgcm9jX2F1Yyh0cnV0aCA9IGxpZ2h0LCAucHJlZC4uLjEpDQoNCnJmX3Rlc3RpbmdfcHJlZCAlPiUgICAgICAgICAgICAgICAgICAgIyB0ZXN0IHNldCBwcmVkaWN0aW9ucyAgKEFTIEFCT1ZFKQ0KICBhY2N1cmFjeSh0cnV0aCA9IGxpZ2h0LCBndWFubykNCmBgYA0KVHVuaW5nIHRoZSBtb2RlbA0KYGBge3J9DQpsaWJyYXJ5KGdsbW5ldCkNCmxpYnJhcnkocnBhcnQucGxvdCkgICMgZm9yIHZpc3VhbGl6aW5nIGEgZGVjaXNpb24gdHJlZQ0KbGlicmFyeSh2aXApICAgICAgICAgIyBmb3IgdmFyaWFibGUgaW1wb3J0YW5jZSBwbG90cw0KDQp0dW5lX3NwZWMgPC0gDQogIGRlY2lzaW9uX3RyZWUoICAjIyB0aGlzIGlzIHRoZSB0eXBlIG9mIG1vZGVsDQogICAgY29zdF9jb21wbGV4aXR5ID0gdHVuZSgpLA0KICAgIHRyZWVfZGVwdGggPSB0dW5lKCkNCiAgKSAlPiUgDQogIHNldF9lbmdpbmUoInJwYXJ0IikgJT4lIA0KICBzZXRfbW9kZSgicmVncmVzc2lvbiIpDQoNCnR1bmVfc3BlYw0KDQp0cmVlX2dyaWQgPC0gZ3JpZF9yZWd1bGFyKGNvc3RfY29tcGxleGl0eSgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICB0cmVlX2RlcHRoKCksDQogICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IDUpDQoNCnRyZWVfZ3JpZA0KDQp0cmVlX2dyaWQgJT4lICMjIHNob3dzIGVhY2ggbGV2ZWwgd2Ugd2lsbCB0dW5lIHRoZSBtb2RlbCBhdA0KICBjb3VudCh0cmVlX2RlcHRoKQ0KDQpzZXQuc2VlZCgyMzQpICMjIGRvbid0IHVuZGVyc3RhbmQgd2hhdCB0aGVzZSBkbz8/DQpyZl9mb2xkcyA8LSB2Zm9sZF9jdihyZl90cmFpbikgICMjIGNyZWF0ZXMgY3Jvc3MtdmFsaWRhdGlvbiBmb2xkcyBmb3IgdHVuaW5nDQoNCnNldC5zZWVkKDM0NSkNCg0KdHJlZV93ZiA8LSB3b3JrZmxvdygpICU+JSAgIyNjcmVhdGVzIHRoZSB3b3JrZmxvdw0KICBhZGRfbW9kZWwodHVuZV9zcGVjKSAlPiUNCiAgYWRkX2Zvcm11bGEoYXZlLnYgfiAuKQ0KDQp0cmVlX3JlcyA8LSAjIyByZXNhbXBsZXMgYW5kIHR1bmVzIG1vZGVsDQogIHRyZWVfd2YgJT4lIA0KICB0dW5lX2dyaWQoDQogICAgcmVzYW1wbGVzID0gcmZfZm9sZHMsDQogICAgZ3JpZCA9IHRyZWVfZ3JpZA0KICAgICkNCg0KdHJlZV9yZXMgICMjIGdpdmVzIHR1bmluZyByZXN1bHRzDQoNCnRyZWVfcmVzICU+JSANCiAgY29sbGVjdF9tZXRyaWNzKCkgICMjIGNvbGxlY3RzIG1ldHJpY3MgZnJvbSB0dW5lZCBtb2RlbHMNCg0KDQp0cmVlX3JlcyAlPiUNCiAgY29sbGVjdF9tZXRyaWNzKCkgJT4lDQogIG11dGF0ZSh0cmVlX2RlcHRoID0gZmFjdG9yKHRyZWVfZGVwdGgpKSAlPiUNCiAgZ2dwbG90KGFlcyhjb3N0X2NvbXBsZXhpdHksIG1lYW4sIGNvbG9yID0gdHJlZV9kZXB0aCkpICsNCiAgZ2VvbV9saW5lKHNpemUgPSAxLjUsIGFscGhhID0gMC42KSArDQogIGdlb21fcG9pbnQoc2l6ZSA9IDIpICsNCiAgZmFjZXRfd3JhcCh+IC5tZXRyaWMsIHNjYWxlcyA9ICJmcmVlIiwgbnJvdyA9IDIpICsNCiAgc2NhbGVfeF9sb2cxMChsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX251bWJlcigpKSArDQogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfZChvcHRpb24gPSAicGxhc21hIiwgYmVnaW4gPSAuOSwgZW5kID0gMCkNCg0KIyMgc3R1YmJpZXN0IHRyZWUgd2l0aCBhIGRlcHRoIG9mIDEgcGVyZm9ybWVkIHRoZSB3b3JzdA0KIyMgZGVlcGVzdCB0cmVlIHdpdGggZGVwdGggb2YgMTUgZGlkIGJldHRlcg0KDQp0cmVlX3JlcyAlPiUNCiAgc2hvd19iZXN0KG1ldHJpYyA9ICJybXNlIikgICMjIHNob3dzIGJlc3QgbW9kZWwgZml0DQoNCmJlc3RfdHJlZSA8LSB0cmVlX3JlcyAlPiUgICMjIHB1bGxzIG91dCBkYXRhIG9uIHRoZSBiZXN0IGZpdA0KICBzZWxlY3RfYmVzdChtZXRyaWMgPSAicm1zZSIpDQoNCmJlc3RfdHJlZSAjIyBzdW1tYXJ5IG9mIGJlc3QgdHJlZSBtb2RlbA0KDQpmaW5hbF93ZiA8LSAjIyBjcmVhdGUgd29ya2Zsb3cgZnJvbSBiZXN0IHRyZWUgbW9kZWwgYWZ0ZXIgdHVuaW5nDQogIHRyZWVfd2YgJT4lIA0KICBmaW5hbGl6ZV93b3JrZmxvdyhiZXN0X3RyZWUpDQoNCmZpbmFsX3dmDQoNCmZpbmFsX2ZpdCA8LSAjIyBjcmVhdGUgZmluYWwgbW9kZWwgZnJvbSBuZXcgZml0DQogIGZpbmFsX3dmICU+JQ0KICBsYXN0X2ZpdChyZl9zcGxpdCkgDQoNCmZpbmFsX2ZpdCAlPiUNCiAgY29sbGVjdF9tZXRyaWNzKCkNCg0KDQpmaW5hbF9maXQgJT4lICAjIyBwbG90IFJPQyBhbmQgY29tcGFyZSBwZXJmb3JtYW5jZSBhZnRlciB0dW5pbmcNCiAgY29sbGVjdF9wcmVkaWN0aW9ucygpICU+JSANCiAgcm9jX2N1cnZlKGZsb3csIGF2ZS52KSAlPiUgIyMjIE5PVCBXT1JLSU5HDQogIGF1dG9wbG90KCkNCg0KZmluYWxfdHJlZSA8LSBleHRyYWN0X3dvcmtmbG93KGZpbmFsX2ZpdCkgICMjIGV4dHJhY3Qgb3VyIGZpbmFsIGZpdCBmb3IgZnV0dXJlIHVzZQ0KZmluYWxfdHJlZQ0KDQpmaW5hbF90cmVlICU+JSAgIyMgY3JlYXRlcyB3b3JrZmxvdyBwbG90DQogIGV4dHJhY3RfZml0X2VuZ2luZSgpICU+JQ0KICBycGFydC5wbG90KHJvdW5kaW50ID0gRkFMU0UpDQoNCmZpbmFsX3RyZWUgJT4lICMjIHNob3dzIHdoaWNoIHZhcmlhYmxlcyBhcmUgbW9zdCBpbXBvcnRhbnQgdG8gdGhlIG1vZGVsIGluIGEgcGxvdA0KICBleHRyYWN0X2ZpdF9wYXJzbmlwKCkgJT4lIA0KICB2aXAoKQ0KDQphcmdzKGRlY2lzaW9uX3RyZWUpDQoNCg0KYGBgDQpCaWdnZXIgUkYgTW9kZWwNCmBgYHtyfQ0KY29yZXMgPC0gcGFyYWxsZWw6OmRldGVjdENvcmVzKCkgIyMgc2VlcyBob3cgbWFueSBjb3JlcyB3ZSBoYXZlIHRvIHByb2Nlc3MgdGhlIGRhdGENCmNvcmVzDQoNCnJmX21vZCA8LSAjIyByYW5kb20gZm9yZXN0IG1vZGVsIGdlbmVyYXRpb24sIHBhcmFsbGVsIHByb2Nlc3Npbmcgb2YgbW9kZWxzDQogIHJhbmRfZm9yZXN0KG10cnkgPSB0dW5lKCksIG1pbl9uID0gdHVuZSgpLCB0cmVlcyA9IDEwMDApICU+JSANCiAgc2V0X2VuZ2luZSgicmFuZ2VyIiwgbnVtLnRocmVhZHMgPSBjb3JlcykgJT4lIA0KICBzZXRfbW9kZSgicmVncmVzc2lvbiIpDQoNCnJmX3JlY2lwZSA8LSAjIyBjcmVhdGUgcmFuZG9tIGZvcmVzdCBtb2RlbCByZWNpcGUNCiAgcmVjaXBlKGF2ZS52IH4gLiwgZGF0YSA9IGRmKSANCiAgDQpyZl93b3JrZmxvdyA8LSAjIyBjcmVhdGUgcmFuZG9tIGZvcmVzdCBtb2RlbCB3b3JrZmxvdw0KICB3b3JrZmxvdygpICU+JSANCiAgYWRkX21vZGVsKHJmX21vZCkgJT4lIA0KICBhZGRfcmVjaXBlKHJmX3JlY2lwZSkNCg0KcmZfbW9kDQoNCnZhbF9zZXQgPC0gdmFsaWRhdGlvbl9zcGxpdChkZiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyYXRhID0gZmxvdywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvcCA9IDAuODApDQp2YWxfc2V0DQojIHNob3cgd2hhdCB3aWxsIGJlIHR1bmVkDQpleHRyYWN0X3BhcmFtZXRlcl9zZXRfZGlhbHMocmZfbW9kKQ0KDQpzZXQuc2VlZCgzNDUpDQpyZl9yZXMgPC0gDQogIHJmX3dvcmtmbG93ICU+JSANCiAgdHVuZV9ncmlkKHZhbF9zZXQsDQogICAgICAgICAgICBncmlkID0gMjUsDQogICAgICAgICAgICBjb250cm9sID0gY29udHJvbF9ncmlkKHNhdmVfcHJlZCA9IFRSVUUpLA0KICAgICAgICAgICAgbWV0cmljcyA9IG1ldHJpY19zZXQocm1zZSkpDQoNCnJmX3JlcyAlPiUgIyMgc2hvd3MgNSBiZXN0IHJhbmRvbSBmb3Jlc3QgbW9kZWxzIG91dCBvZiB0aGUgMjUgY2FuZGlkYXRlcw0KICBzaG93X2Jlc3QobWV0cmljID0gInJtc2UiKQ0KDQphdXRvcGxvdChyZl9yZXMpICAjIyBwbG90IHJlc3VsdHMNCg0KYGBgDQoNCmBgYHtyfQ0KcmZfYmVzdCA8LSAjIyBjcmVhdGVzIG1vZGVsIHdpdGggYmVzdCBwcmVkaWN0b3JzDQogIHJmX3JlcyAlPiUgDQogIHNlbGVjdF9iZXN0KG1ldHJpYyA9ICJybXNlIikNCnJmX2Jlc3QNCg0KcmZfcmVzICU+JSAjIyBjb2xsZWN0cyBkYXRhIGZvciBST0MgY3VydmUgcGxvdA0KICBjb2xsZWN0X3ByZWRpY3Rpb25zKCkNCg0KcmZfYmVzdCRtdHJ5IDwtIGFzLmludGVnZXIocmZfYmVzdCRtdHJ5KQ0KDQoNCiMjTk9UIFdPUktJTkcNCnJmX2F1YyA8LSAjIyBjcmVhdGVzIHNldCBvZiBtb2RlbHMgd2l0aCBiZXN0IG1vZGVsIGFuZCBtb2RlbCBtb2RlbCBmb3IgY29tcGFyaXNvbg0KICByZl9yZXMgJT4lIA0KICBjb2xsZWN0X3ByZWRpY3Rpb25zKHBhcmFtZXRlcnMgPSByZl9iZXN0KSAlPiUgDQogIHJvY19jdXJ2ZShtdHJ5LCAucHJlZCkgJT4lIA0KICBtdXRhdGUobW9kZWwgPSAiUmFuZG9tIEZvcmVzdCIpICANCg0KIyNOT1QgV09SS0lORw0KYmluZF9yb3dzKHJmX2Jlc3QsIHJmX3JlcykgJT4lICMjIHBsb3RzIG1vZGVsIGNvbXBhcmlzb25zIG9uIFJPQyBjdXJ2ZQ0KICBnZ3Bsb3QoYWVzKHggPSAxIC0gc3BlY2lmaWNpdHksIHkgPSBzZW5zaXRpdml0eSwgY29sID0gbW9kZWwpKSArIA0KICBnZW9tX3BhdGgobHdkID0gMS41LCBhbHBoYSA9IDAuOCkgKw0KICBnZW9tX2FibGluZShsdHkgPSAzKSArIA0KICBjb29yZF9lcXVhbCgpICsgDQogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfZChvcHRpb24gPSAicGxhc21hIiwgZW5kID0gLjYpDQpgYGANCg0KYGBge3J9DQojIyMjIyMjIyMjIyMjIyMjIGxhc3QgbW9kZWwgYWZ0ZXIgdHVuaW5nIA0KDQojIHRoZSBsYXN0IG1vZGVsDQpsYXN0X3JmX21vZCA8LSANCiAgcmFuZF9mb3Jlc3QobXRyeSA9IDgsIG1pbl9uID0gNywgdHJlZXMgPSAxMDAwKSAlPiUgDQogIHNldF9lbmdpbmUoInJhbmdlciIsIG51bS50aHJlYWRzID0gY29yZXMsIGltcG9ydGFuY2UgPSAiaW1wdXJpdHkiKSAlPiUgDQogIHNldF9tb2RlKCJyZWdyZXNzaW9uIikNCg0KIyB0aGUgbGFzdCB3b3JrZmxvdw0KbGFzdF9yZl93b3JrZmxvdyA8LSANCiAgcmZfd29ya2Zsb3cgJT4lIA0KICB1cGRhdGVfbW9kZWwobGFzdF9yZl9tb2QpDQoNCiMgdGhlIGxhc3QgZml0DQpzZXQuc2VlZCgzNDUpDQpsYXN0X3JmX2ZpdCA8LSANCiAgbGFzdF9yZl93b3JrZmxvdyAlPiUgDQogIGxhc3RfZml0KHJmX3NwbGl0KQ0KDQpsYXN0X3JmX2ZpdA0KDQpsYXN0X3JmX2ZpdCAlPiUgICMjIGNvbGxlY3QgbWV0cmljcyBmcm9tIGZpbmFsIG1vZGVsDQogIGNvbGxlY3RfbWV0cmljcygpDQoNCmxhc3RfcmZfZml0ICU+JSAjIyB1cGRhdGVzIG1vZGVsIGZpdA0KICBleHRyYWN0X2ZpdF9wYXJzbmlwKCkgJT4lIA0KICB2aXAobnVtX2ZlYXR1cmVzID0gMjApDQoNCiMjTk9UIFdPUktJTkcNCmxhc3RfcmZfZml0ICU+JSAjIyBwbG90cyBiZXN0IFJPQyBjdXJ2ZSwgd2l0aCBiZXN0IHNldCBvZiBoeXBlcnBhcmFtZXRlcnMgYXMgcHJlZGljdG9ycw0KICBjb2xsZWN0X3ByZWRpY3Rpb25zKCkgJT4lIA0KICByb2NfY3VydmUoYXZlLnYsIC5wcmVkLi4uMSkgJT4lIA0KICBhdXRvcGxvdCgpDQoNCmBgYA0KDQo=